
#include <BRender>
#include <BMaterial>
#include <BCapsule>
#include "member_BCapsule.h"
#include "member_BGeometry.h"
#include "member_BShape.h"

using namespace BWE;

#define member					(*(member_BCapsule*)_ptr)
#define member_allocate()		_ptr = new member_BCapsule(this)
#define member_release()		delete (member_BCapsule*)_ptr

BCapsule::BCapsule()
{
	member_allocate();
	member.prepare();
	this->dirty();
}
BCapsule::BCapsule(BReal radius, BReal height)
{
	member_allocate();
	member.height = height;
	member.radius = radius;
	this->dirty();
}
BCapsule::BCapsule(BReal radius, BReal height, int sides)
{
	member_allocate();
	member.height = height;
	member.radius = radius;
	setSides(sides);
	this->dirty();
}
BCapsule::BCapsule(BReal radius, BReal height, int sides, int heightSegments, int radiusSegments)
{
	member_allocate();
	member.height = height;
	member.radius = radius;
	member.heightSegments = heightSegments;
	member.radiusSegments = radiusSegments;
	setSides(sides);
	this->dirty();
}
BCapsule::BCapsule(const BCapsule& other)
{
	member_allocate();
	this->copy(&other);
	member.prepare();
	this->dirty();
}
BCapsule::~BCapsule()
{
	member_release();
}

void BCapsule::copy(const BShape* other)
{
	BShape::copy(other);
	if (const BCapsule* capsule = dynamic_cast<const BCapsule*>(other))
	{
		member.height = capsule_member(capsule)->height;
		member.radius = capsule_member(capsule)->radius;
		member.sides = capsule_member(capsule)->sides;
		member.heightSegments = capsule_member(capsule)->heightSegments;
		member.radiusSegments = capsule_member(capsule)->radiusSegments;
		member.sines = capsule_member(capsule)->sines;
		member.coses = capsule_member(capsule)->coses;
		member.flankSize = capsule_member(capsule)->flankSize;
		member.topBegin = capsule_member(capsule)->topBegin;
		member.topSize = capsule_member(capsule)->topSize;
		member.bottomBegin = capsule_member(capsule)->bottomBegin;
		member.bottomSize = capsule_member(capsule)->bottomSize;
	}
}
BShape* BCapsule::clone() const
{
	BCapsule* capsule = new BCapsule();
	capsule->copy(this);
	return capsule;
}

void BCapsule::setSides(int sides)
{
	if (member.sides != sides)
	{
		member.sides = sides;
		member.prepare();
		this->dirty();
	}
}
int BCapsule::sides() const
{
	return member.sides;
}

void BCapsule::setRadius(BReal radius)
{
	if (member.radius != radius)
	{
		member.radius = radius;
		this->dirty();
	}
}
BReal BCapsule::radius() const
{
	return member.radius;
}

void BCapsule::setHeight(BReal height)
{
	if (member.height != height)
	{
		member.height = height;
		this->dirty();
	}
}
BReal BCapsule::height() const
{
	return member.height;
}

void BCapsule::setHeightSegments(int heightSegments)
{
	if (member.heightSegments != heightSegments)
	{
		member.heightSegments = heightSegments;
		this->dirty();
	}
}
int BCapsule::heightSegments() const
{
	return member.heightSegments;
}

void BCapsule::setRadiusSegments(int radiusSegments)
{
	if (member.radiusSegments != radiusSegments)
	{
		member.radiusSegments = radiusSegments;
		this->dirty();
	}
}
int BCapsule::radiusSegments() const
{
	return member.radiusSegments;
}

BReal BCapsule::halfHeight() const
{
	return member.height / 2;
}

BGeometry* BCapsule::split(const BString& name)
{
	this->flush();
	if (name == "top")
	{
		BGeometry* geometry = new BGeometry("top");
		geometry->copy(this);

		BVectorArray* vertices = new BVectorArray();
		vertices->append(geometry_member(this)->vertices->data() + member.topBegin, member.topSize);
		geometry->setVertices(vertices);

		BVectorArray* normals = new BVectorArray();
		normals->append(geometry_member(this)->normals->data() + member.topBegin, member.topSize);
		geometry->setNormals(normals);

		if (geometry_member(this)->colors.valid())
		{
			BColorArray* colors = new BColorArray();
			colors->append(geometry_member(this)->colors->data() + member.topBegin, member.topSize);
			geometry->setColors(colors);
		}
		for (int i = 0; i < geometry_member(this)->coords.size(); i++)
		{
			if (geometry_member(this)->coords[i].valid())
			{
				BCoordArray* coords = new BCoordArray();
				coords->append(geometry_member(this)->coords[i]->data() + member.topBegin, member.topSize);
				geometry->setTexCoords(coords, i);
			}
		}
		if (BElement* topElement = this->element("top"))
		{
			BElement* element = new BElement(*topElement);
			if (element->indices())
			{
				BIntArray* indices = new BIntArray(*element->indices());
				for (int i = 0; i < indices->size(); i++)
					(*indices)[i] -= member.topBegin;
				element->setIndices(indices);
			}
			geometry->addElement(element);
		}
		if (BElement* topCentralElement = this->element("top-center"))
		{
			BElement* element = new BElement(*topCentralElement);
			if (element->indices())
			{
				BIntArray* indices = new BIntArray(*element->indices());
				for (int i = 0; i < indices->size(); i++)
					(*indices)[i] -= member.topBegin;
				element->setIndices(indices);
			}
			geometry->addElement(element);
		}
		return geometry;
	}
	if (name == "flank")
	{
		BGeometry* geometry = new BGeometry("flank");
		geometry->copy(this);

		BVectorArray* vertices = new BVectorArray();
		vertices->append(geometry_member(this)->vertices->data(), member.flankSize);
		geometry->setVertices(vertices);

		BVectorArray* normals = new BVectorArray();
		normals->append(geometry_member(this)->normals->data(), member.flankSize);
		geometry->setNormals(normals);

		if (geometry_member(this)->colors.valid())
		{
			BColorArray* colors = new BColorArray();
			colors->append(geometry_member(this)->colors->data(), member.flankSize);
			geometry->setColors(colors);
		}
		for (int i = 0; i < geometry_member(this)->coords.size(); i++)
		{
			if (geometry_member(this)->coords[i].valid())
			{
				BCoordArray* coords = new BCoordArray();
				coords->append(geometry_member(this)->coords[i]->data(), member.flankSize);
				geometry->setTexCoords(coords, i);
			}
		}
		if (BElement* element = this->element("flank"))
		{
			geometry->addElement(element);
		}
		return geometry;
	}
	if (name == "bottom")
	{
		BGeometry* geometry = new BGeometry("bottom");
		geometry->copy(this);

		BVectorArray* vertices = new BVectorArray();
		vertices->append(geometry_member(this)->vertices->data() + member.bottomBegin, member.bottomSize);
		geometry->setVertices(vertices);

		BVectorArray* normals = new BVectorArray();
		normals->append(geometry_member(this)->normals->data() + member.bottomBegin, member.bottomSize);
		geometry->setNormals(normals);

		if (geometry_member(this)->colors.valid())
		{
			BColorArray* colors = new BColorArray();
			colors->append(geometry_member(this)->colors->data() + member.bottomBegin, member.bottomSize);
			geometry->setColors(colors);
		}
		for (int i = 0; i < geometry_member(this)->coords.size(); i++)
		{
			if (geometry_member(this)->coords[i].valid())
			{
				BCoordArray* coords = new BCoordArray();
				coords->append(geometry_member(this)->coords[i]->data() + member.bottomBegin, member.bottomSize);
				geometry->setTexCoords(coords, i);
			}
		}
		if (BElement* bottomElement = this->element("bottom"))
		{
			BElement* element = new BElement(*bottomElement);
			if (element->indices())
			{
				BIntArray* indices = new BIntArray(*element->indices());
				for (int i = 0; i < indices->size(); i++)
					(*indices)[i] -= member.bottomBegin;
				element->setIndices(indices);
			}
			geometry->addElement(element);
		}
		if (BElement* bottomCentralElement = this->element("bottom-center"))
		{
			BElement* element = new BElement(*bottomCentralElement);
			if (element->indices())
			{
				BIntArray* indices = new BIntArray(*element->indices());
				for (int i = 0; i < indices->size(); i++)
					(*indices)[i] -= member.bottomBegin;
				element->setIndices(indices);
			}
			geometry->addElement(element);
		}
		return geometry;
	}
	return 0;
}

BVector BCapsule::inertia(BReal mass) const
{
	BReal lx = 2 * member.radius;
	BReal ly = 2 * member.radius;
	BReal lz = member.height;

	const BReal x2 = lx * lx;
	const BReal y2 = ly * ly;
	const BReal z2 = lz * lz;

	const BReal scale = BReal(1) / 12;

	BReal inex = mass * (y2 + z2) * scale;
	BReal iney = mass * (x2 + z2) * scale;
	BReal inez = mass * (x2 + y2) * scale;

	return BVector(inex, iney, inez);
}
BVector BCapsule::vertex(const BVector& direct) const
{
	BReal radius = member.radius;
	BReal height = member.height;
	BReal halfHeight = height / 2;
	if (halfHeight < REAL_EPSILON)
	{
		return direct * radius;
	}
	BVector base;
	if (direct.z() > 0)
		base.z() = halfHeight;
	else
		base.z() = -halfHeight;
	BVector axis(0, 0, height);
	BVector foot = direct - axis * (direct.cross(axis) / (height * height));
	if (foot.length2() < REAL_EPSILON)
	{
		return base + direct * radius;
	}
	return base + foot.normal() * radius;
}

void BCapsule::refresh(BBox& box)
{
	BSpace space;
	BReal z = member.height / 2 + member.radius;
	space.min().set(-member.radius, -member.radius, -z);
	space.max().set(member.radius, member.radius, z);
	member.rebuild();
	box = space;
}
void BCapsule::render(BRender& render)
{
	BGeometry::render(render);
}
